//! 当前配置文件加载支持json yaml toml 还需要实现http加载
//! 文件编解码未实现
//! 错误处理需要细化

use serde;
use serde_json;
use serde_urlencoded;
use serde_yaml;
use toml;

//------------------------------------错误处理-------------------------------------
#[derive(Debug)]
pub struct Error {
    message: String,
    error: String,
}
impl Error {
    fn new(msg: String, err: String) -> Self {
        Error {
            message: msg,
            error: err,
        }
    }
}

impl Error {
    pub fn message(&self) -> String {
        self.message.clone()
    }

    pub fn error(&self) -> String {
        self.error.clone()
    }

    pub fn to_string<'a>(&self) -> String {
        format!(r#""message":"{}","error":"{}""#, self.message, self.error)
    }
}

//------------------------------格式获取

pub fn load_config_from_path<F, P>(t: P) -> Result<F, Error>
where
    P: file,
    F: for<'a> serde::de::Deserialize<'a> + for<'de> serde::Deserialize<'de>,
{
    let file_type = t.get_file_type();
    if let FileType::NONE = file_type {
        return Err(Error::new(
            "无法根据输入路径解析文件格式".to_string(),
            "unknow file type".to_string(),
        ));
    }
    let f: F = match file_type {
        FileType::JSON => {
            let data = t.get_file_content();
            if let Err(e) = data {
                return Err(Error::new(e.to_string(), format!("{:?}", e.kind())));
            }
            let buf = data.unwrap();
            let err: Result<F, serde_json::Error> = serde_json::from_str(&buf);
            match err {
                Ok(o) => o,
                Err(e) => return Err(Error::new(e.to_string(), format!("{:?}", e))),
            }
        }
        FileType::HTTP => {
            // FIXME HTTP加载方式是错误的，纯属凑数，以后会实现网络文件加载
            let err: Result<F, serde::de::value::Error> =
                serde_urlencoded::from_str(t.get_file_path());
            match err {
                Ok(o) => o,
                Err(e) => return Err(Error::new(e.to_string(), format!("{:?}", e))),
            }
        }
        FileType::YAML => {
            let data = t.get_file_content();
            if let Err(e) = data {
                return Err(Error::new(e.to_string(), format!("{:?}", e)));
            }
            let buf = data.unwrap();
            let err: Result<F, serde_yaml::Error> = serde_yaml::from_str(&buf);
            match err {
                Ok(o) => o,
                Err(e) => return Err(Error::new(e.to_string(), format!("{:?}", e))),
            }
        }
        FileType::TOML => {
            let data = t.get_file_content();
            if let Err(e) = data {
                return Err(Error::new(e.to_string(), format!("{:?}", e)));
            }
            let err: Result<F, toml::de::Error> = toml::from_str(&data.unwrap());
            match err {
                Ok(o) => o,
                Err(e) => return Err(Error::new(e.to_string(), format!("{}", e))),
            }
        }
        FileType::NONE => {
            return Err(Error::new(
                "无法根据输入路径解析文件格式".to_string(),
                "unknow file type".to_string(),
            ))
        }
    };
    Ok(f)
}

pub enum FileType {
    JSON,
    HTTP,
    YAML,
    TOML,
    NONE,
}

#[allow(non_camel_case_types)]
pub trait file {
    fn get_file_type(&self) -> FileType;
    fn get_file_path(&self) -> &str;
    fn get_file_content(&self) -> std::io::Result<String>;
}

pub struct File {
    path: &'static str,
}

impl File {
    pub fn new(p: &'static str) -> File {
        File { path: p }
    }
}

impl file for File {
    fn get_file_type(&self) -> FileType {
        if &self.path[0..4].to_lowercase() == "http" {
            return FileType::HTTP;
        } else {
            let filetype = self.path[self.path.len() - 4..].to_lowercase();
            return match filetype.as_str() {
                "json" => FileType::JSON,
                "toml" => FileType::TOML,
                "yaml" => FileType::YAML,
                _ => FileType::NONE,
            };
        }
    }
    fn get_file_path(&self) -> &str {
        self.path
    }
    fn get_file_content(&self) -> std::io::Result<String> {
        std::fs::read_to_string(self.path)
    }
}
